# frozen_string_literal: true

# Security::VulnerabilityReadsFinder
#
# Used to filter Vulnerability records for Vulnerabilities API from vulnerability_reads table
#
# Arguments:
#   vulnerable: any object that has a #vulnerabilities method that returns a collection of `Vulnerability`s
#   params: optional! a hash with one or more of the following:
#     project_ids: if `vulnerable` includes multiple projects (like a Group), this filter will restrict
#                   the vulnerabilities returned to those in the group's projects that also match these IDs
#     image: only return vulnerabilities with these location images
#     report_types: only return vulnerabilities from these report types
#     severities: only return vulnerabilities with these severities
#     states: only return vulnerabilities in these states
#     scanner: only return vulnerabilities with these external_id
#     scanner_id: only return vulnerabilities with these scanner_ids
#     has_resolution: only return vulnerabilities that have resolution
#     has_issues: only return vulnerabilities that have issues linked
#     cluster_agent_id: only return vulnerabilities with these cluster_agent_ids
#     sort: return vulnerabilities ordered by severity_asc or severity_desc

module Security
  class VulnerabilityReadsFinder
    include FinderMethods

    def initialize(vulnerable, params = {})
      @params = params
      @vulnerable = vulnerable
      @vulnerability_reads = vulnerable.vulnerability_reads
    end

    def execute
      use_unnested_filters
      filter_by_projects
      filter_by_image
      filter_by_report_types
      filter_by_severities
      filter_by_states
      filter_by_scanner_external_id
      filter_by_scanner_ids
      filter_by_resolution
      filter_by_issues
      filter_by_cluster_agent_id

      sort
    end

    private

    attr_reader :params, :vulnerable, :vulnerability_reads

    def use_unnested_filters
      return unless use_unnested_filters?

      @vulnerability_reads = vulnerability_reads.use_unnested_filters
    end

    def use_unnested_filters?
      vulnerable.is_a?(Project) || vulnerable.is_a?(Group)
    end

    def filter_by_projects
      return unless params[:project_id].present?

      @vulnerability_reads = vulnerability_reads.for_projects(params[:project_id])
    end

    def filter_by_report_types
      return unless params[:report_type].present?

      @vulnerability_reads = vulnerability_reads.with_report_types(params[:report_type])
    end

    def filter_by_severities
      return unless params[:severity].present?

      @vulnerability_reads = vulnerability_reads.with_severities(params[:severity])
    end

    def filter_by_states
      return unless params[:state].present?

      @vulnerability_reads = vulnerability_reads.with_states(params[:state])
    end

    def filter_by_scanner_ids
      return unless params[:scanner_id].present?

      @vulnerability_reads = vulnerability_reads.by_scanner_ids(params[:scanner_id])
    end

    def filter_by_scanner_external_id
      return unless params[:scanner].present?

      @vulnerability_reads = vulnerability_reads.with_scanner_external_ids(params[:scanner])
    end

    def filter_by_resolution
      return unless params[:has_resolution].in?([true, false])

      @vulnerability_reads = vulnerability_reads.with_resolution(params[:has_resolution])
    end

    def filter_by_issues
      return unless params[:has_issues].in?([true, false])

      @vulnerability_reads = vulnerability_reads.with_issues(params[:has_issues])
    end

    def filter_by_image
      return if vulnerable.is_a?(InstanceSecurityDashboard) || !params[:image].present?

      @vulnerability_reads = vulnerability_reads.with_container_image(params[:image])
    end

    def filter_by_cluster_agent_id
      return unless params[:cluster_agent_id].present?

      # TODO: One background migration to fill casted_cluster_agent_id migration is finished,
      # we can remove this. See: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/89915
      cluster_agent_ids_as_string = Array.wrap(params[:cluster_agent_id]).map(&:to_s)
      @vulnerability_reads = vulnerability_reads.with_cluster_agent_ids(cluster_agent_ids_as_string)
    end

    def sort
      @vulnerability_reads.order_by(params[:sort])
    end
  end
end
